package com.culture.config.util;

import java.util.Date;
import java.util.List;
import java.util.Objects;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

import com.culture.config.common.util.BeanUtil;
import com.culture.config.common.util.ExceptionUtil;
import com.culture.config.common.util.RandomUtil;
import com.culture.config.common.util.ReqeustsUtil;
import com.culture.config.dao.entity.User;

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
 
@Slf4j
@UtilityClass
public class AuthenticationUtil {
	
	public static final String BASIC_AUTH_KEY_NAME = "Authorization";
	public static final String CRSF_TOKEN_NAME = "_crsf";
	public static final String AUTH_TOKEN_NAME = "_auth_token";
	public static final String ACCESS_TOKEN_NAME = "_access_token";	
	
	public static String encodePassword(String rawPwd){
		BCryptPasswordEncoder bcrypt = new BCryptPasswordEncoder();
		return bcrypt.encode(rawPwd);
	}	

	public static String getCurrentUserName(String defaultName) {
		String name = getCurrentUserName();
		if(StringUtils.isBlank(name)){
			return defaultName;
		}
		return name;
	}

	public static String getCurrentUserName() {
		if(Objects.isNull(SecurityContextHolder.getContext())) {
			return null;
		}
		Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
		if(Objects.isNull(authentication)){
			return null;
		}		
		return authentication.getName();		 
	}
	
	public static String getUserName(Authentication authentication) {		
		if(Objects.isNull(authentication)){
			return null;
		}		
		return authentication.getName();		 
	}
	
	public static void setUpdateFieldInfo(Object bean) {
		if(Objects.isNull(bean)) {
			return;
		}	
	
		BeanUtil.setProperty(bean, "updateBy", AuthenticationUtil.getCurrentUserName("system"));
		BeanUtil.setProperty(bean, "updateTime", new Date());
	}
	
	public static void setCreateFieldInfo(Object bean) {
		if(Objects.isNull(bean)) {
			return;
		}
		BeanUtil.setProperty(bean, "createBy", AuthenticationUtil.getCurrentUserName("system"));
		BeanUtil.setProperty(bean, "createTime", new Date());
	}
	
	public static void setInsertFieldInfo(Object bean) {
		if(Objects.isNull(bean)) {
			return;
		}
		setCreateFieldInfo(bean);
		setUpdateFieldInfo(bean);		
	}
	
	public static void validateBasicAuth(HttpHeaders headers, User user) {
		ExceptionUtil.businessExceptionWhenNull(headers, "No HttpHeaders");
		ExceptionUtil.businessExceptionWhenNull(user, "Not Authorized");
		List<String> authorizationTokens =  headers.get(BASIC_AUTH_KEY_NAME);
		ExceptionUtil.businessExceptionWhenEmpty(authorizationTokens, "No basicAuth token");
		String defaultToken = authorizationTokens.get(0);
		ExceptionUtil.businessExceptionWhenBlank(defaultToken, "BasicAuth token is empty");
		log.info("validateBasicAuth: {},{}",defaultToken, user.getAccessKey());
		ExceptionUtil.businessExceptionWhenTrue(!StringUtils.equals(defaultToken, user.getAccessKey())  , "Authorized failed.");		
	}
	
	public static String saveCrsfToken(HttpServletRequest request) {
		String crsfToken = DigestUtils.md5Hex(RandomUtil.randomCode(16));
		request.getSession().setAttribute(CRSF_TOKEN_NAME, crsfToken);
		return crsfToken;
	}
	
	public static String validateCrsfToken() {	
		HttpServletRequest request = ReqeustsUtil.getHttpServletRequest();
		ExceptionUtil.businessExceptionWhenNull(request, "Validate CrsfToken exception.");
		return validateCrsfToken(request);
	}
	
	public static String validateCrsfToken(HttpServletRequest request) {	
		Object crsfToken = request.getSession().getAttribute(CRSF_TOKEN_NAME);
		ExceptionUtil.businessExceptionWhenNull(crsfToken, "No CrsfToken");
		String reqCrsfToken = getCrsfToken(request);
		log.info("validateCrsfToken:{},{}",crsfToken, reqCrsfToken);
		ExceptionUtil.businessExceptionWhenTrue(!StringUtils.equals(reqCrsfToken, crsfToken.toString()), "Validate CrsfToken failed.");
		return reqCrsfToken;
	}
	
	public static String getCrsfToken(HttpServletRequest request) {
		String reqCrsfToken = request.getParameter(CRSF_TOKEN_NAME);
		if(StringUtils.isNotBlank(reqCrsfToken)) {
			return reqCrsfToken;
		}
		reqCrsfToken = request.getHeader(CRSF_TOKEN_NAME);
		if(StringUtils.isNotBlank(reqCrsfToken)) {
			return reqCrsfToken;
		}		
		String refererParams = StringUtils.substringAfter(request.getHeader("Referer"), "?");
		if(!StringUtils.contains(refererParams, CRSF_TOKEN_NAME)) {
			return reqCrsfToken;
		}
		String[] urlParams = StringUtils.split(refererParams,"&");		 
		if(urlParams == null) {
			return reqCrsfToken;
		}		
		for(String s: urlParams) {		 
			if(StringUtils.startsWith(s, CRSF_TOKEN_NAME)) {
				reqCrsfToken = StringUtils.substringAfter(s, "=");
				break;
			}	 
		}		
		return reqCrsfToken;
	}	
	
	public static String getCrsfTokenParamsPair(HttpServletRequest request) {
		return StringUtils.join(CRSF_TOKEN_NAME,"=", getCrsfToken(request));
	}
	
	public static String saveAuthToken(HttpServletRequest request) {
		String authToken = DigestUtils.md5Hex(RandomUtil.randomCode(16));
		request.getSession().setAttribute(AUTH_TOKEN_NAME, authToken);
		return authToken;
	}	
	
	public static void validateAuthToken() {	
		HttpServletRequest request = ReqeustsUtil.getHttpServletRequest();
		validateAuthToken(request);
	}
	
	public static void validateAuthToken(HttpServletRequest request) {	
		Object authToken = request.getSession().getAttribute(AUTH_TOKEN_NAME);
		ExceptionUtil.businessExceptionWhenNull(authToken, "No AuthToken");
		String userToken = getUserToken(request);
		log.info("validateAuthToken:{},{}",authToken, userToken);
		ExceptionUtil.businessExceptionWhenTrue(!StringUtils.equals(userToken, authToken.toString()), "Validate AuthToken failed.");
	}	
	
	private static String getUserToken(HttpServletRequest request) {		
		String userToken = request.getParameter(AUTH_TOKEN_NAME);
		if(StringUtils.isNotBlank(userToken)) {
			return userToken;
		}
		return request.getHeader(AUTH_TOKEN_NAME);
	}	
	
	public static void logout(HttpServletRequest request) {
		request.getSession().removeAttribute(AUTH_TOKEN_NAME);	
		request.getSession().removeAttribute(CRSF_TOKEN_NAME);		
		request.getSession().invalidate();
	}

}
